package kubernetescli

import (
	"context"
	"errors"
	"log"
	"strconv"
	"time"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/Kindling-project/chaos-backend/tools/wrapper"
	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
)

type networkchaos struct {
}

func (n *networkchaos) ToChaosVO(obj *interface{}) []api.Chaos {
	return wrapper.NetworkChaosToChaosVO(obj)
}

func (n *networkchaos) List() (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}
	result := v1alpha1.NetworkChaosList{}
	err := client.
		Get().
		Resource("networkchaos").
		Namespace("train-ticket").
		Do(context.Background()).
		Into(&result)
	return result, err
}

func (n *networkchaos) Create(vo *api.Chaos) (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}

	n.DeleteAll()

	networkchaos := buildNetworkChaos(vo, defaultNetworkChaosParam)
	result := v1alpha1.NetworkChaos{}
	err := client.
		Post().
		Namespace(networkchaos.Namespace).
		Resource("networkchaos").
		Body(networkchaos).
		Do(context.Background()).
		Into(&result)
	return result, err
}

func (n *networkchaos) DeleteAll() (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}

	result := v1alpha1.NetworkChaosList{}
	err := client.
		Get().
		Resource("networkchaos").
		Namespace("train-ticket").
		Do(context.Background()).
		Into(&result)

	if err != nil {
		return nil, err
	}

	var lastChaos v1alpha1.NetworkChaos
	for _, chaos := range result.Items {
		if v, ok := chaos.Labels["kindling-chaos"]; ok && v == "train-ticket" {
			result := v1alpha1.NetworkChaos{}
			err := client.
				Delete().
				Resource("networkchaos").
				Name(chaos.Name).
				Namespace(chaos.Namespace).
				Do(context.Background()).Into(&result)
			if err != nil {
				return nil, err
			}
			lastChaos = chaos
		}
	}

	return lastChaos, err
}

var defaultNetworkChaosParam = v1alpha1.NetworkChaos{
	Spec: v1alpha1.NetworkChaosSpec{
		Direction: v1alpha1.To,
	},
}

func addDnsAsTarget(params *v1alpha1.NetworkChaos) {
	params.Spec.Direction = v1alpha1.Both
	params.Spec.Target = &v1alpha1.PodSelector{}
	params.Spec.Target.Mode = v1alpha1.AllMode
	params.Spec.Target.Selector.LabelSelectors = map[string]string{
		"k8s-app": "kube-dns",
	}
	params.Spec.Target.Selector.Namespaces = []string{"kube-system"}
}

func buildNetworkChaos(vo *api.Chaos, params v1alpha1.NetworkChaos) *v1alpha1.NetworkChaos {
	var resource = params.DeepCopy()
	ts := time.Now().Unix()

	resource.Name = "networkchaos" + "-" + strconv.FormatInt(ts, 10)
	resource.Namespace = "train-ticket"
	// Mark labels
	resource.Labels = map[string]string{
		"kindling-chaos": "train-ticket",
	}
	resource.Annotations = map[string]string{
		"describe":       vo.Desc,
		"complex-action": vo.ComplexAction,
	}

	addTcParameter(resource, v1alpha1.NetworkChaosAction(vo.ChaosAction))

	for _, param := range vo.Params {
		switch param.Name {
		case "instance":
			resource.Spec.Selector.LabelSelectors = map[string]string{
				"app": param.Value,
			}
			resource.Spec.Mode = v1alpha1.AllMode

			resource.Annotations["target"] = param.Value
		case "option":
			if param.Value == "dns" {
				addDnsAsTarget(resource)
			}
		}
	}

	return resource
}

func addTcParameter(params *v1alpha1.NetworkChaos, action v1alpha1.NetworkChaosAction) {
	params.Spec.Action = action

	// TODO Support UserDefined Param Value
	switch action {
	case v1alpha1.DelayAction:
		params.Spec.TcParameter = v1alpha1.TcParameter{
			Delay: &v1alpha1.DelaySpec{
				Latency:     "200ms",
				Correlation: "0",
				Jitter:      "10ms",
			},
		}
	case v1alpha1.BandwidthAction:
		params.Spec.TcParameter = v1alpha1.TcParameter{
			Bandwidth: &v1alpha1.BandwidthSpec{
				Rate:   "20kbps",
				Limit:  80000,
				Buffer: 80000,
			},
		}
	case v1alpha1.LossAction:
		params.Spec.TcParameter = v1alpha1.TcParameter{
			Loss: &v1alpha1.LossSpec{
				Loss:        "30",
				Correlation: "0",
			},
		}
	default:
		log.Printf("Action [%s] is not supported yet, change to default action delay", action)
		params.Spec.Action = v1alpha1.DelayAction
		params.Spec.TcParameter = v1alpha1.TcParameter{
			Delay: &v1alpha1.DelaySpec{
				Latency:     "200ms",
				Correlation: "0",
				Jitter:      "1ms",
			},
		}
	}
}
